.. _Целочисленная арифметика:

Целочисленная арифметика
========================

.. index::
  Целочисленная арифметика
  overflow
  переполнение
  потеря точности
  underflow
  if
  while
  ++
  --
  -
  *
  /
  %
  ^^
  +=
  -=
  \=
  *=
  ^^=
  %=

Мы видели, что операторы if и while позволяют программам принимать решения, используя тип bool в виде логических выражений.
В этойглаве мы рассмотрим арифметические операции над целыми типами в языке D. Эти возможности позволят нам писать гораздо более полезные программы.
Хотя арифметические операции являются частью нашей повседневной жизни и на самом деле
просты, существуют очень важные понятия, о которых программист должен знать,
чтобы создавать правильные программы: разрядность типа, переполнение (перенос) и усечение.
Прежде чем идти дальше, я хотел бы обобщить арифметические операции в следующей таблице в качестве справочного материала

.. list-table:: Основные арифметические операции
   :widths: 25 25 25
   :header-rows: 1
   :align: center

   * - Оператор
     - Эффект
     - Пример
   * - ++
     - увеличить на единицу
     - ++variable
   * - --
     - уменьшить на единицу
     - --variable
   * - +
     - сложение
     - var1 + var2
   * - -
     - вычитание
     - var1 - var2
   * - *
     - умножение
     - var1 * var2
   * - /
     - деление
     - var1 / var2
   * - %
     - получение остатка делениея
     - var1 % var2
   * - ^^
     - возведение в степень
     - var1 ^^ var2


У большинства из этих операторов есть аналоги, к которым прикреплен знак =:

* +=
* -=
* \*=
* /=
* %=
* ^^=

Разница с этими операторами заключается в том, что они присваивают результат левой части:

.. code-block:: d

    var1 += 10;


Это выражение складывает значение переменной и 10 и присваивает результат
переменной . В итоге значение переменной будет увеличено на 10. Это эквивалентно следующему выражению:

.. code-block:: d

    var1 = var1 + 10;

Я хотел бы также кратко изложить здесь две важные концепции, прежде чем подробнее остановиться на них ниже.
Переполнение: Не все значения могут поместиться в переменную данного типа. Если значение слишком
велико для переменной, мы говорим, что переменная переполняется. Например, переменная
типа ubyte может иметь значения только в диапазоне от 0 до 255; поэтому при присвоении 260
переменная переполняется, и ее значение становится равным 4.
*(Примечание: В отличие от некоторых других языков, таких как C и C++, переполнение для типов со знаком разрешено в D. У него такое же
поведение, как и у неподписанных типов.)*

Аналогично, переменная не может иметь значение, которое меньше минимального значения ее типа.
Усечение: Целочисленные типы не могут иметь значений с дробными частями. *Например, значение выражения int 3/2 равно 1, а не 1.5.*

Мы ежедневно сталкиваемся с арифметическими операциями без особых сюрпризов: если бублик стоит
1 доллар, два бублика стоят 2 доллара; если четыре бутерброда стоят 15 долларов, один бутерброд стоит 3,75 доллара и т.д.
К сожалению, с арифметическими операциями в компьютерах все не так просто. Если мы не понимаем, как значения хранятся в компьютере, мы можем
быть удивлены, увидев, что долг компании сокращается до 1,7 миллиарда долларов, когда она берет взаймы
Еще 3 миллиарда долларов сверх существующего долга в 3 миллиарда долларов! Или когда коробка мороженого
предназначена для 4 детей, арифметическая операция может утверждать, что 2 коробок было бы достаточно для 11 детей!
Программисты должны понимать, как целые числа хранятся в компьютерах.

Целочисленные типы
------------------

.. index::
  Целочисленные типы

Целочисленные типы - это типы, которые могут иметь только целые значения, такие как -2, 0, 10 и т.д. Эти
типы не могут иметь дробных частей, как в 2.5. Все целочисленные типы, которые мы видели в главе "Основные типы" (стр. 8), являются следующими:


.. list-table:: Title
   :widths: 25 25 50
   :header-rows: 1
   :align: center

   * - тип
     - диапазон
     - начальное значение
   * - byte
     - знаковый 8 bits
     - 0
   * - ubyte
     - без знака 8 бит
     - 0
   * - short
     - знаковый  16 бит
     - 0
   * - ushort
     - без знака 16 бит
     - 0
   * - int
     - знаковый  32 бит
     - 0
   * - uint
     - знаковый  32 бит
     - 0
   * - long
     - без знака 64 бит
     - 0
   * - ulong
     - знаквый  64 бит
     - 0

Буква u в начале имен типов означает "без знака" и указывает, что такие типы не могут иметь значений меньше нуля.
Хотя они равны 0 ; 0L и 0LU являются явными константами , введенными как longи ulong , соответственно.

Количество битов определенного типа
-----------------------------------

В современных компьютерных системах наименьшая единица информации называется битом. На
физическом уровне бит представлен электрическими сигналами в определенных
точках схемы компьютера. Бит может находиться в одном из двух состояний, соответствующих
различным напряжениям в области, определяющей этот конкретный бит. Эти два состояния
произвольно определяются как имеющие значения 0 и 1. В результате бит может иметь одно из
этих двух значений.
Поскольку существует не так много концепций, которые могут быть представлены всего двумя состояниями, немного
не очень полезный тип. Он может быть полезен только для концепций с двумя состояниями, такими как
орел или решка или включен или выключен выключатель света.
Если мы рассматриваем два бита вместе, общий объем информации, который может быть
представлен, умножается. Исходя из того, что каждый бит имеет значение 0 или 1 по отдельности,
существует в общей сложности 4 возможных состояния. Предполагая, что левая и правая цифры
представляют первый и второй бит соответственно, эти состояния равны 00, 01, 10 и 11.
Давайте добавим еще один бит, чтобы лучше увидеть этот эффект; три бита могут быть в 8 разных
состояния: 000, 001, 010, 011, 100, 101, 110, 111. Как можно видеть, каждый добавленный бит удваивает
общее количество состояний, которые могут быть представлены.
Значения, которым соответствуют эти восемь состояний, определяются соглашениями.
В следующей таблице показаны эти значения для знакового и беззнакового представления 3 бит:

.. index::
    byte
    ubyte
    short
    ushort
    int
    uint
    long
    ulong

.. list-table:: Title
   :widths: 25 25 50
   :header-rows: 1
   :align: center

   * - состояние
     - беззнаковое значение
     - знаковое значение
   * - 000
     - 0
     - 0
   * - 001
     - 1
     - 1
   * - 1
     - знаковый  16 бит
     - 0
   * - 010
     - 2
     - 2
   * - 011
     - 3
     - 3
   * - 100
     - 4
     - -4
   * - 101
     - 5
     - -3
   * - 110
     - 6
     - 2
   * - 111
     - 7
     - -1

.. list-table:: Title
   :widths: 25 25 25 25 25
   :header-rows: 1
   :align: center

   * - количество бит
     - количество различных значений
     - тип в языке D
     - Минимальное значение
     - Максимальное значение
   * - 1
     - 1
     -
     -
     -
   * - 2
     - 4
     - "
     -
     -
   * - 3
     - 8
     -
     -
     -
   * - 4
     - 16
     -
     -
     -
   * - 5
     - 32
     -
     -
     -
   * - 6
     - 64
     -
     -
     -
   * - 7
     - 128
     -
     -
     -
   * - 8
     - 256
     - byte/ubyte
     - -128/0
     - 127/255
   * - ...
     - ...
     - ...
     - ...
     - ...
   * - 16
     - 65536
     - short/ushort
     - -32768/0
     - 32767/65536
   * - ...
     - ...
     - ...
     - ...
     - ...
   * - 32
     - 4294967296
     - int/uint
     - -2147483648/0
     - 2147483647/4294967295
   * - ...
     - ...
     - ...
     - ...
     - ...
   * - 64
     - 18446744073709551616
     - long/ulong
     - -9223372036854775808/0
     - 9223372036854775807/18446744073709551615


Я пропустил много строк в таблице и указал подписанные и неподписанные версии
общих типов, которые имеют одинаковое количество битов в одной строке (например, int и
uint находятся в 32-разрядной строке).

Выбор типа
-----------

D не имеет 3-разрядного типа. Но такой гипотетический тип может иметь только 8 различных значений.
Он может представлять только такие понятия, как значение кубика или
номер дня недели.
С другой стороны, хотя uint является очень большим типом, он не может представлять
концепцию идентификационного номера для каждого живого человека, поскольку его максимальное значение меньше
, чем население земли в 7 миллиардов человек. long и ulong было бы более чем
достаточно для представления многих концепций.
Как правило, до тех пор, пока нет конкретной причины не делать этого, вы можете использовать int
для целочисленных значений.

Переполнение
----------------

Тот факт, что типы могут содержать значения только в пределах ограниченного диапазона, может привести
к неожиданным результатам. Например, хотя добавление двух переменных uint со значениями
по 3 миллиарда каждая должно привести к получению 6 миллиардов, поскольку эта сумма больше
максимального значения, которое может содержать переменная uint (около 4 миллиардов), эта сумма переполняется.
Без какого-либо предупреждения сохраняется только разница между 6 и 4 миллиардами. (Немного
точнее, 6 минус 4,3 миллиарда.)

Округление (Отбрасывание дробной части)
---------------------------------------

Поскольку целые числа не могут иметь значений с дробными частями, они теряют часть после
десятичной точки. Например, предполагая, что коробка мороженого рассчитана на 4 детей,
хотя на самом деле для 11 детей потребовалось бы 2,75 коробки, дробная часть этого значения
не может быть сохранена в целочисленном типе, поэтому значение становится равным 2.
Далее в этой главе я покажу ограниченные методы, помогающие снизить риск переполнения и
усечения.

.min и .max
-----------

Ниже я воспользуюсь преимуществами свойств .min и .max, которые мы уже видели
в главе "Основные типы" (стр. 8). Эти свойства предоставляют
минимальные и максимальные значения, которые может иметь целочисленный тип.

Инкремент: ++
-------------

.. index::
  ++
  increment

Этот оператор используется с одной переменной (в более общем смысле, с одним
выражением) и записывается перед именем этой переменной. Он увеличивает
значение этой переменной на 1:

.. code-block:: d

  import std.stdio;

  void main()
  {
      int number = 10;
      ++number;
      writeln("New value: ", number);
  }



.. code-block:: console

    Новое значение: 11

Оператор инкремента эквивалентен использованию оператора сложения и присваивания
со значением 1:

.. code-block:: d

    number += 1;
    // или
    number = number + 1

Если результат операции увеличения больше максимального значения  этого типа, результат переполняется и становится минимальным значением.
Мы можем увидеть этот эффект, увеличив переменную, которая изначально имеет значение int.max :

.. code-block:: d

    import std.stdio;

    void main()
    {
        writeln("minimum int value   : ", int.min);
        writeln("maximum int value   : ", int.max);
        int number = int.max;
        writeln("before the increment: ", number);
        ++number;
        writeln("after the increment : ", number);
    }





.. code-block:: console

    минимальное значение int
    максимальное значение int 2147483647
    перед приращением 2147483647
    after the increment -2147483648



Это очень важное наблюдение, потому что значение изменяется от
максимального к минимальному в результате увеличения и без какого-либо предупреждения!
Этот эффект называется переполнением. Мы увидим аналогичные эффекты при других операциях.

Декремент  --
--------------

.. index::
  --
  decrement


Этот оператор аналогичен оператору приращения; разница в том, что значение уменьшается на 1:

.. code-block:: d

    --numder;
    // значение уменьшается на 1

Операция уменьшения эквивалентна использованию оператора вычитания и
присваивания со значением 1:

.. code-block:: d

    number -= 1;
    // или
    number = number - 1

Аналогично оператору ++, если значение является минимальным значением для начала, оно
становится максимальным значением. Этот эффект также называется переполнением.


Сложение: +
-----------

.. index::
  +
  сложение
  add

Этот оператор используется с двумя выражениями и добавляет их значения:

.. code-block:: d

    import std.stdio;

  void main()
  {
      int number_1 = 12;
      int number_2 = 100;
      writeln("Result: ", number_1 + number_2);
      writeln("With a constant expression: ", 1000 + number_2);
  }


.. code-block:: console

    Результат: 112
    С постоянным выражением: 1100

Если сумма двух выражений больше максимального значения этого типа,
оно переполняется и становится значением, которое меньше обоих выражений:

.. code-block:: d

    import std.stdio;

    void main()
    {
        // 3 billion each
        uint number_1 = 3000000000;
        uint number_2 = 3000000000;
        writeln("maximum value of uint: ", uint.max);
        writeln("             number_1: ", number_1);
        writeln("             number_2: ", number_2);
        writeln("                  sum: ", number_1 + number_2);
        writeln("OVERFLOW! The result is not 6 billion!");
    }

.. code-block:: console

      максимальное значение uint: 4294967295
      number_1: 3000000000
      number_2: 30000000000
      сумма: 1705032704
      ПЕРЕПОЛНЕНИЕ! В результате получается не 6 миллиардов!


Вычитание: -
-------------

.. index::
  -
  substruction
  вычитание

Этот оператор используется с двумя выражениями и дает разницу между первым и вторым:

.. code-block:: d

    import std.stdio;
    void main() {
      int number_1 = 10;
      int number_2 = 20;
      writeln(number_1 - number_2);
      writeln(number_2 - number_1);

.. code-block:: c++

    -10
    10

Давайте перепишем программу, используя тип uint:

.. code-block:: d

    import std.stdio;

    void main()
    {
        uint number_1 = 10;
        uint number_2 = 20;
        writeln("PROBLEM! uint cannot have negative values:");
        writeln(number_1 - number_2);
        writeln(number_2 - number_1);
    }
.. code-block:: console

    ПРОБЛЕМА! uint не может иметь отрицательных значений:
    4294967286
    10


Рекомендуется использовать типы со знаком для представления понятий, которые когда-либо могут быть использованы в операции вычитания


Умножение
-------------

.. index::
  *
  умножение
  multiply

Этот оператор умножает значения двух выражений; результат снова может быть переполнен:

.. code-block:: d

    import std.stdio;
    void main() {
      uint number_1 = 6;
      uint number_2 = 7;
      writeln(number_1 * number_2);
    }


Деление:
------------

.. index::
  /
  division
  деление

Этот оператор делит первое выражение на второе. Поскольку целочисленные типы не могут иметь дробных значений, дробная часть значения отбрасывается.
Этот эффект называется усечением. В результате следующая программа выводит 3, а не 3.5:

.. code-block:: d

    import std.stdio;
    void main() {
      writeln(7/2);
    }

-- code-block:: console

    3

Для вычислений, в которых важны дробные части, вместо целых чисел следует использовать типы с плавающей запятой, которые будут рассмотрены в следующей главе


Остаток (модуль): %
--------------------

.. index::
  %
  остаток
  reminder

Этот оператор делит первое выражение на второе выражение и выдает остаток от деления:

.. code-block:: d

    import std.stdio;
    void main() {
      writeln(10 % 6);
    }

.. code-block:: console

    4

Обычное применение этого оператора заключается в определении того, является ли значение нечетным или
четным. Поскольку остаток от деления четного числа на 2 всегда равен 0,
достаточно сравнить результат с 0, чтобы провести это различие:

.. code-block:: d

    if ((number % 2) == 0)
    {
        writeln("even number");
    }
    else
    {
        writeln("odd number");
    }

Степень: ^^
------------

.. index::
  ^^

Этот оператор возводит первое выражение в степень второго выражения.
Например, возведение 3 в степень 4 означает умножение 3 само по себе в 4 раза:

.. code-block:: d

    import std.stdio;
    void main() {
      writeln(3 ^^ 4);
    }

.. code-block:: console

    81

Арифметические операции с присваиванием
----------------------------------------

Все операторы, которые принимают два выражения, имеют аналоги с использованием присваивания.
Эти операторы присваивают результат обратно выражению, которое находится в левой части:

.. code-block:: d

    import std.stdio;
    void main() {
      int number = 10;
      number += 20;  // как number = number + 20 результат 30
      number -=5;    // как number = number -5   результат 25
      number *= 2;   // как number = number * 2  результат 50
      number /= 3;   // как number = number / 3  результат 16
      number %= 7    // как number number % 7    результат 2
      number ^^= 6   // как number = number ^^ 6 результат 64
      writeln(number)
    }

.. code-block:: c++

    64


Отрицание: -
-------------

.. index::
  -

Этот оператор преобразует значение выражения из отрицательного в положительное или из положительного в отрицательный.
Иными словами изменяет знак числа

.. code-block:: d


    import std.stdio;
    void main() {
      int number_1 = 1;
      int number_2 = -2;
      writeln(-number_1);
      writeln(-number_2);
    }

.. code-block:: console

    -1
    2

Тип результата этой операции совпадает с типом выражения.
Поскольку типы без знака не могут иметь отрицательных значений, результат использования этого
оператора с типами без знака может быть неожиданным:

.. code-block:: d

    uint number = 1;
    writeln("отрицание: ", -numder);

Типом -number также является uint, который не может иметь отрицательных значений:

.. code-block:: console

    отрицание: 4294967295

Операция плюс: +
-----------------

.. index::
  +

Этот оператор не имеет эффекта и существует только для симметрии с оператором отрицания.
Положительные значения остаются положительными, а отрицательные значения остаются отрицательными:

.. code-block:: d

    import std.stdio;
    void main() {
      int number_1 = 1;
      int number_2 = -2;
      writeln(+number_1);
      writeln(+number_2);
    }

.. code-block::

    1
    -2

Последующее увеличение: ++
--------------------------

.. index::
  ++

*Примечание: Если нет веской причины не делать этого, всегда используйте обычный оператор приращения
(который иногда называют оператором предварительного приращения).*


В отличие от обычного оператора приращения, он записывается после выражения
и по-прежнему увеличивает значение выражения на 1. Разница в том, что
операция постинкремента возвращает старое значение выражения. Чтобы увидеть эту
разницу, давайте сравним ее с обычным оператором инкремента:

.. code-block:: d

    import std.stdio;
    void main() {
      int incremented_regularly = 1;
      writeln(++incremented_regularly);
      writeln(incremented_regularly);
      // выводит 2
      // выводит 2
      int post_incremented = 1;
      // Увеличивается, но используется его старое значение:
      writeln(post_incremented++);
      // выводит 1
      writeln(post_incremented);
      // выводит 2
    }

.. code-block:: console

    2
    2
    1
    2

Приведенный выше оператор writeln(post_incremented++); эквивалентен следующему коду:

.. code-block:: d

    int old_value = post_incremented;
    ++post_incremented;
    writeln(old_value);
    // выводит 1


Последующие умньшение --
------------------------

.. index::
  --

*Примечание: Если нет веской причины не делать этого, всегда используйте обычный оператор уменьшения
(который иногда называют оператором предварительного уменьшения).*


Этот оператор ведет себя так же, как оператор после увеличения, за исключением того, что он уменьшает.

Приоритет оператора
--------------------

.. index::
  Приоритет оператора

Все операторы, которые мы обсуждали выше, использовались в операциях над их
владеть только одним или двумя выражениями. Однако, подобно логическим выражениям,
обычно эти операторы объединяются для формирования более сложных арифметических
выражений:

.. code-block:: d

    int value = 77;
    int result = (((value + 8) * 3) / (value - 1)) % 5;

Как и в случае с логическими операторами, арифметические операторы также подчиняются
правилам приоритета операторов. Например, оператор * имеет приоритет над оператором +. По этой
причине, когда круглые скобки не используются (например, в выражении value + 8 * 3),
оператор * вычисляется перед оператором +. В результате это выражение
становится эквивалентом значения + 24 , которое сильно отличается от
(value + 8) * 3 .
Использование круглых скобок полезно как для обеспечения правильных результатов, так и для
передачи намерения кода программистам, которые могут работать над ним в будущем.
Таблица приоритета операторов будет представлена позже в книге (ссылка на страницу).

Решение находится на странице :ref:`Присваивание и порядок вычисления выражений<Присваивание и порядок вычисления выражений>`.

Обнаружение переполнения
-------------------------

.. index::
  Обнаружение переполнения

Хотя в нем используются функции (:ref:`Функции<глава_32_start>`) и параметры ref (:ref:`Параметры функций<глава_35_start>`), которые мы
еще не рассматривали, я хотел бы упомянуть здесь, что модуль core.checkedint
содержит арифметические функции, которые обнаруживают переполнение. Вместо операторов
как и + и -, этот модуль использует функции: adds и addu для сложения со знаком и без знака
, muls и mulu для умножения со знаком и без знака, subs и subu
для вычитания со знаком и без знака, и negs для отрицания.
Например, предполагая, что a и b являются двумя переменными int, следующий код
определит, вызвало ли их добавление переполнение:

.. code-block:: d

    import core.checkedint;
    void main() {
    // Давайте вызовем переполнение для целей тестирования
      int a = int.max - 1;
      int b = 2;
    // Эта переменная станет "true", если операция добавления
    // внутри функции "adds" завершится переполнением:
      bool hasOverflowed = false;
      int result = adds(a, b, hasOverflowed);
      if (hasOverflowed) {
    // Мы не должны использовать 'result', потому что он переполнен
    // ...
      } else {
        // Мы можем использовать 'result'
        // ...
      }
    }

Существует также модуль std.experimental.checkedint, который определяет проверяемый
шаблон, но на данном этапе книги как его использование, так и реализация слишком продвинуты

1. http://dlang.org/phobos/core_checkedint.html
2. https://dlang.org/phobos/std_experimental_checkedint.html

Предотвращение переполнения
---------------------------

.. index::
  Предотвращение переполнения


Если размер результата операции не соответствует размерам типа результата, то
ничего нельзя сделать. Иногда, хотя размер результата вычислений соответсвует размеру типа конечного результата,
промежуточные вычисления могут переполняться и приводить к неправильным результатам.
В качестве примера давайте предположим, что нам нужно посадить яблоню на 1000 квадратных
метров площади размером 40 на 60 километров. Сколько деревьев нужно?
Когда мы решаем эту задачу на бумаге, мы видим, что результат равен 40000 раз
60000 делится на 1000, что равно 2,4 миллионам деревьев. Давайте напишем программу, которая
выполняет это вычисление:

.. code-block:: d

    import std.stdio;
    void main() {
      int width = 40000;
      int length = 60000;
      int areaPerTree = 1000;
      int treesNeeded = width * length / areaPerTree;
      writeln("Number of trees needed: ", treesNeeded);
  }

.. code-block:: console

    Number of trees needed: -1894967

Не говоря уже о том, что это даже близко не так, результат также меньше нуля! В этом случае
промежуточное значение ширины вычисления * длины переполняется, и последующее
вычисление / площади на дерево приводит к неверному результату.
Одним из способов избежать переполнения в этом примере является изменение порядка
операций:

.. code-block:: c++

    int treesNeeded = width / areaPerTree * length ;

The result would now be correct:

.. code-block:: console

    Number of trees needed: 2400000

Причина, по которой этот метод работает, заключается в том, что все этапы вычисления
теперь соответствуют типу int.
Пожалуйста, обратите внимание, что это не полное решение, поскольку на этот раз
промежуточное значение подвержено усечению, что может существенно повлиять на результат
при некоторых других вычислениях. Другим решением может быть использование
типа с плавающей запятой вместо целочисленного типа: float , double или real .

Предотвращение усечения
-----------------------


Изменение порядка операций также может быть решением проблемы усечения.
Интересный пример усечения можно увидеть, разделив и умножив
значение на одно и то же число. Мы ожидали бы, что результат 10/9*9 будет равен 10, но
получается 9:

.. code-block:: c++

    import std.stdio;
    void main() {
      writeln(10 / 9 * 9);
    }

.. code-block:: console

    9

Результат будет правильным, если использовать перестановку операций,чтобы избежать усечения

.. code-block:: c++

    writeln(10 * 9 / 9);

.. code-block:: console

    10


Это тоже не полное решение: на этот раз промежуточное вычисление может
привести к переполнению. Использование типа с плавающей запятой может быть другим решением для
усечения в определенных вычислениях.


12.1 Упражения
--------------

1. Напишите программу, которая принимает от пользователя два целых числа, выводит целое число
частное, полученное в результате деления первого на второе, а также выводит
остаток. Например, когда вводятся 7 и 3, попросите программу вывести
следующее уравнение:
7 = 3 * 2 + 1

2. Измените программу, чтобы она выводила более короткие выходные данные, когда остаток равен 0.
Например, при вводе 10 и 5 она должна выводить не "10 = 5 * 2 + 0", а только
следующее:
10 = 5 * 2

3. Напишите простой калькулятор, который поддерживает четыре основные арифметические операции.
Пусть программа разрешит выбрать операцию из меню и применит эту
операцию к двум введенным значениям. В этой программе вы можете игнорировать переполнение и
усечение.

4. Напишите программу, которая выводит значения от 1 до 10, каждое в отдельной строке,
за исключением значения 7. Не используйте повторяющиеся строки, как в следующем
коде:

.. code-block:: c++

    import std.stdio;
    void main() {
    // Do not do this!
      writeln(1);
      writeln(2);
      writeln(3);
      writeln(4);
      writeln(5);
      writeln(6);
      writeln(8);
      writeln(9);
      writeln(10);
  }

Вместо этого представьте переменную, значение которой увеличивается в цикле. Возможно, вам потребуется воспользоваться оператором is not equal to != здесь.
Решения приведены на странице :ref:`Арифметические операции с целыми числами<Арифметические операции с целыми числами>`.


